// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
玩家起始擁有 20 個代幣,而玩家只要能夠取得任一數量之額外的代幣(balance > 20),即可通關
Solidity 內並不像 Python 一樣內建大數運算,換言之,Solidity 的運算是會溢位 (Overflow) 的,老樣子,來看看下面的測試碼吧
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract overflow {
uint256 b = 0;
function _overflow() public view returns(uint256) {
return b - 1;
}
}
Solidity uint256 的計算範圍為 0 ~ (2^256)-1,負數或超過範圍的運算都會使其溢位進而得到預料之外的結果,了解了這關的核心之後,接著就進入程式碼分析吧。
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
這關的程式碼和上一關一樣並不長,所以我們只要重點看這個帶有漏洞的 transfer 就可以了,可以發現裡面做運算時沒有預防 Overflow 的機制,所以我們可以很輕鬆的通過 require 的審查進而取得額外的代幣;player 一開始有 20 個代幣,我們可以執行 transfer 函數,讓 player 轉錢給一個隨意地址,而傳送數量則訂為 21, balances[msg.sender] - _value 將會溢位成為 2^256-1,通過 require 後,自身的 balances 則會扣除 21 顆 Token,同樣會溢位成為 2^256-1,空地址會獲得 21 個代幣,OK,那就立刻來動手實作吧。
我們需要一個空地址來做為轉帳對象,這邊就直接拿關卡作者的 address 來充當空地址了 XD
player2 = "0x31a3801499618d3c4b0225b9e06e228d4795b55d"
接著執行 transfer 吧
await contract.transfer(player2, 21)
◕_◕ ◕_◕ ◕_◕ ◕_◕
剛剛有提到,Solidity 在運算時含有溢位的問題,而預防方式有兩個
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract overflow {
uint256 b = 0;
function _overflow() public view returns(uint256) {
return b - 1;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract overflow {
uint256 b = 0;
function _overflow() public view returns(uint256) {
return b - 1;
}
}
所以只要將開發版本改為使用 0.8.0 以上,就不用擔心溢位問題啦,但是溢位運算檢測也是一個步驟,在區塊鏈系統上,每個步驟都是要算錢的,因此 Solidity 多出了一個新的語法 unchecked 這個語法是當你非常確定某個部分不可能產生溢位,則可以使用 unchecked 來省去溢位運算檢測,藉此來降低 gas fee。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract overflow {
uint256 b = 0;
function _overflow() public view returns(uint256) {
unchecked {
return b - 1;
}
}
}
可以從圖片上看到,編譯器並沒有執行檢測意味的動作。
https://docs.soliditylang.org/en/v0.8.13/080-breaking-changes.html